﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace Rdz.Network
{
	public class RdzSocket : IDisposable
	{
		private const string CannotConnectMessage = "Failed to connect to Remote Address.";
		private const string NotConnectedMessage = "Not connected to Remote Address.";
		private Socket Sok = null;

		#region "Constructor"
		/// <summary>
		/// Initialize and connect socket to Remote IP Address and Port.
		/// </summary>
		/// <param name="RemoteIPAddress">Remote IP Address to connect to.</param>
		/// <param name="Port">Port to connect to.</param>
		/// <param name="addressFamily">Specifies the addressing scheme.</param>
		/// <param name="socketType">Specifies the type of socket.</param>
		/// <param name="protocolType">Specifies the supported protocol.</param>
		/// <param name="ResolveHostEntry">True if you want to resolve host entry against DNS.</param>
		public RdzSocket(string RemoteIPAddress, int Port, AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, bool ResolveHostEntry)
		{
			IPHostEntry IPs = null;
			if (ResolveHostEntry)
			{
				//A little longer here
				IPs = Dns.GetHostEntry(RemoteIPAddress);
				foreach (IPAddress IP in IPs.AddressList)
				{
					IPEndPoint IpEP = new IPEndPoint(IP, Port);
					Socket SokTmp = new Socket(addressFamily, socketType, protocolType);
					SokTmp.Connect(IpEP);

					if (SokTmp.Connected)
					{ Sok = SokTmp; return; }
				}
			}
			else
			{
				IPAddress IP = IPAddress.Parse(RemoteIPAddress);
				IPEndPoint IpEP = new IPEndPoint(IP, Port);
				Socket SokTmp = new Socket(addressFamily, socketType, protocolType);
				SokTmp.Connect(IpEP);

				if (SokTmp.Connected)
				{ Sok = SokTmp; return; }
			}
			if (Sok == null)
				throw new Exception(CannotConnectMessage);
		}
		/// <summary>
		/// Initialize and connect socket to Remote IP Address and Port.
		/// </summary>
		/// <param name="RemoteIPAddress">Remote IP Address to connect to.</param>
		/// <param name="Port">Port to connect to.</param>
		/// <param name="socketInformation">Encapsulated socket information details.</param>
		/// <param name="ResolveHostEntry">True if you want to resolve host entry against DNS.</param>
		public RdzSocket(string RemoteIPAddress, int Port, SocketInformation socketInformation, bool ResolveHostEntry)
		{
			IPHostEntry IPs = null;
			if (ResolveHostEntry)
			{
				//A little longer here
				IPs = Dns.GetHostEntry(RemoteIPAddress);

				foreach (IPAddress IP in IPs.AddressList)
				{
					IPEndPoint IpEP = new IPEndPoint(IP, Port);
					Socket SokTmp = new Socket(socketInformation);
					SokTmp.Connect(IpEP);
					if (SokTmp.Connected)
					{
						Sok = SokTmp;
						return;
					}
				}
			}
			else
			{
				IPAddress IP = IPAddress.Parse(RemoteIPAddress);
				IPEndPoint IpEP = new IPEndPoint(IP, Port);
				Socket SokTmp = new Socket(socketInformation);
				SokTmp.Connect(IpEP);

				if (SokTmp.Connected)
				{ Sok = SokTmp; return; }
			}
			if (Sok == null)
				throw new Exception(CannotConnectMessage);
		}
		#endregion

		#region "Properties"
		public bool Connected
		{
			get
			{
				if (Sok == null)
					return false;
				else return Sok.Connected;
			}
		}
		#endregion

		/// <summary>
		/// Send text to socket and receive the returns in StringBuilder.
		/// </summary>
		/// <param name="SendText">Text which will be sent to socket.</param>
		/// <param name="EncodingType">Sent and Received text encoding.</param>
		/// <returns></returns>
		public StringBuilder SendAndReceiveText(string SendText, Encoding EncodingType, Int32 BufferLength)
		{
			if (Sok == null) { throw new Exception(NotConnectedMessage); };
			Byte[] BytesSent = EncodingType.GetBytes(SendText);
			Byte[] BytesReceived = new Byte[BufferLength];
			Int32 bytesCount = 0;
			StringBuilder ReceivedText = new StringBuilder();

			Sok.Send(BytesSent, BytesSent.Length, 0);
			do
			{
				bytesCount = Sok.Receive(BytesReceived, BytesReceived.Length, 0);
				ReceivedText.Append(EncodingType.GetString(BytesReceived, 0, bytesCount));
			} while (bytesCount > 0 && Sok.Available > 0);
			return ReceivedText;
		}

		public void KeepAlive(int TimeOut)
		{
			Sok.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, TimeOut);
		}

		#region "Destructor"
		bool disposedValue = false;
		protected virtual void Dispose(bool disposing)
		{
			if (!this.disposedValue)
			{
				if (disposing)
				{ }
				if (Sok != null)
				{
					Sok.Close();
				}
			}

			disposedValue = true;
		}
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}
		#endregion
	}
}
